package com.neusoft.hifly.gateway.utils;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;

import com.google.common.collect.Maps;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSObject;
import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.Payload;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;

import com.neusoft.hifly.gateway.domain.TokenVO;
import com.neusoft.hifly.lock.utils.Log;
import net.minidev.json.JSONObject;

/**
 * JWT组成 第一部分我们称它为头部（header),第二部分我们称其为载荷（payload)，第三部分是签证（signature)。
 */
public class JwtUtil {
	public static final String TOKEN_KEY = "access-token";

	public static final String ORGAN_CODE = "oc";
	public static final String ORGAN_ID = "gid";
	public static final String ORG_ID = "oid";
	public static final String POSITION_ID = "pid";
	public static final String ROLE_ID = "rid";
	public static final String USER_ID = "uid";
	public static final String USER_NAME = "un";
	public static final String LOGIN_NAME = "ln";
	public static final String IAT = "iat";
	public static final String EXT = "ext";
	public static final String STATE = "state";
	public static final String DATA = "data";

	/**
	 * 初始化head部分的数据为(第一部分) { "alg":"HS256", "type":"JWT" }
	 */
	private static final JWSHeader HEADER = new JWSHeader(JWSAlgorithm.HS256, JOSEObjectType.JWT, null, null, null,
			null, null, null, null, null, null, null, null);

	/**
	 * JWT的组成：Header + payload + signature Payload(载荷)的组成信息，私有声明(标准中注册的声明和公共的声明并未使用)
	 *
	 * @param secret
	 *            secret
	 * @param expire
	 *            expire
	 * @param vo
	 *            Token信息
	 * @return token
	 */
	public static String createPayLoad(final String secret, final long expire, final TokenVO vo) {
		final Map<String, Object> payload = Maps.newHashMap();
		LocalDateTime localDateTime = LocalDateTime.now();
		if (StringUtils.isNotEmpty(vo.getOrganCode())) {
			payload.put(JwtUtil.ORGAN_CODE, vo.getOrganCode());
		}
		payload.put(JwtUtil.ORGAN_ID, vo.getOrganLogicId());
		payload.put(JwtUtil.ORG_ID, vo.getOrgLogicId());
		payload.put(JwtUtil.POSITION_ID, vo.getPositionLogicId());
		payload.put(JwtUtil.USER_ID, vo.getUserLogicId());
		if (StringUtils.isNotEmpty(vo.getUserName())) {
			payload.put(JwtUtil.USER_NAME, vo.getUserName());
		}
		if (StringUtils.isNotEmpty(vo.getLoginName())) {
			payload.put(JwtUtil.LOGIN_NAME, vo.getLoginName());
		}
		if (StringUtils.isNotEmpty(vo.getRoleLogicIds())) {
			payload.put(JwtUtil.ROLE_ID, vo.getRoleLogicIds());
		}

		// 生成时间:当前
		payload.put(JwtUtil.IAT, localDateTime.toInstant(ZoneOffset.ofHours(8)).toEpochMilli());
		// 过期时间60分钟(单位毫秒)
		payload.put(JwtUtil.EXT, localDateTime.toInstant(ZoneOffset.ofHours(8)).toEpochMilli() + expire);
		final String token = JwtUtil.createToken(secret, payload);
		Log.debug("token = " + token);
		return token;
	}

	/**
	 * 生成token，该方法只在用户登录成功后调用
	 *
	 * @param secret
	 *            secret
	 * @param payload
	 *            Map集合，可以存储用户id，token生成时间，token过期时间等自定义字段
	 * @return token字符串,若失败则返回null
	 */
	public static String createToken(final String secret, final Map<String, Object> payload) {
		String tokenString = null;
		// 创建一个JWS Object(第二部分)
		final JWSObject jwsObject = new JWSObject(JwtUtil.HEADER, new Payload(new JSONObject(payload)));
		try {
			// 将jwsObject进行HMAC签名，相当于加密(第三部分)
			jwsObject.sign(new MACSigner(secret.getBytes()));
			tokenString = jwsObject.serialize();
		} catch (final JOSEException e) {
			Log.error(String.format("签名失败: {}", e.getMessage()));
			e.printStackTrace();
		}
		return tokenString;
	}

	/**
	 * 校验token是否合法，返回Map集合,集合中主要包含 state状态码 data鉴权成功后从token中提取的数据 该方法在过滤器中调用，每次请求API时都校验
	 *
	 * @param secret
	 *            secret
	 * @param token
	 *            token
	 * @return Map<String, Object>
	 */
	public static Map<String, Object> validToken(final String secret, final String token) {
		final Map<String, Object> resultMap = Maps.newHashMap();
		if (StringUtils.isEmpty(token)) {
			resultMap.put(JwtUtil.STATE, TokenState.INVALID.toString());
			return resultMap;
		}
		try {
			final JWSObject jwsObject = JWSObject.parse(token);
			// palload就是JWT构成的第二部分不过这里自定义的是私有声明(标准中注册的声明, 公共的声明)
			final Payload payload = jwsObject.getPayload();
			final JWSVerifier verifier = new MACVerifier(secret.getBytes());
			if (jwsObject.verify(verifier)) {
				final JSONObject jsonObject = payload.toJSONObject();
				// token检验成功（此时没有检验是否过期）
				resultMap.put(JwtUtil.STATE, TokenState.VALID.toString());
				// 若payload包含ext字段，则校验是否过期
				if (jsonObject.containsKey(JwtUtil.EXT)) {
					final long extTime = Long.valueOf(jsonObject.get(JwtUtil.EXT).toString());
					final long curTime = System.currentTimeMillis();
					// 过期了
					if (curTime > extTime) {
						resultMap.clear();
						resultMap.put(JwtUtil.STATE, TokenState.EXPIRED.toString());
					}
				}
				resultMap.put(JwtUtil.DATA, jsonObject);
			} else {
				// 检验失败
				resultMap.put(JwtUtil.STATE, TokenState.INVALID.toString());
			}
		} catch (final Exception e) {
			e.printStackTrace();
			// token格式不合法导致的异常
			resultMap.clear();
			resultMap.put(JwtUtil.STATE, TokenState.INVALID.toString());
		}
		return resultMap;
	}

	/**
	 * 解密Token中的数据
	 * 
	 * @param secret
	 *            秘钥
	 * @param token
	 *            token
	 * @param key
	 *            键
	 * @return 值
	 */
	public static Object getByKey(String secret, String token, String key) {
		Map<String, Object> resultMap = JwtUtil.validToken(secret, token);

		TokenState state = TokenState.getTokenState((String) resultMap.get(JwtUtil.STATE));
		switch (state) {
			case VALID:
				Log.warn("有效token");
				JSONObject jsonObject = (JSONObject) resultMap.get(JwtUtil.DATA);
				if (jsonObject != null && jsonObject.containsKey(key)) {
					return jsonObject.get(key);
				} else {
					return null;
				}
			case EXPIRED:
				Log.warn("令牌过期");
				return null;
			case INVALID:
				Log.warn("非法令牌");
				return null;
			default:
				return null;
		}
	}

}